<?php

namespace App\Admin\Grid;

use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

class Model
{
    /**
     * 模型实例
     *
     * @var EloquentModel
     */
    protected $model;

    /**
     * 查询
     *
     * @var array|\Illuminate\Support\Collection
     */
    protected $queries = [];

    /**
     * 模型的排序字段
     *
     * @var array
     */
    protected $sort;

    /**
     * 数据
     *
     * @var array
     */
    protected $data = [];

    /**
     * 默认每页数量
     *
     * @var int
     */
    protected $perPage = 15;

    /**
     * 是否使用分页
     *
     * @var bool
     */
    protected $usePaginate = true;

    /**
     * 前端调整每页数的字段名
     *
     * @var string
     */
    protected $perPageName = '_per_page';

    /**
     * 请求中设置页数的字段名
     *
     * @var string
     */
    protected $pageName = '_page';

    /**
     * 前端调整排序的排序字段名
     *
     * @var string
     */
    protected $sortName = '_sort';

    /**
     * 数据集合的回调处理
     *
     * @var \Closure
     */
    protected $collectionCallback;

    /**
     * Model constructor.
     * @param EloquentModel $model
     */
    public function __construct(EloquentModel $model)
    {
        $this->model = $model;
        $this->queries = collect();
    }

    /**
     * 返回Eloquent模型实例
     *
     * @return EloquentModel
     */
    public function eloquent()
    {
        return $this->model;
    }

    /**
     * 返回Eloquent实例对应的表名
     *
     * @return string
     */
    public function getTable()
    {
        return $this->model->getTable();
    }

    /**
     * 从传入的Eloquent模型实例获取数据
     *
     * @return array
     * @throws \Exception
     */
    public function buildData()
    {
        if (empty($this->data)) {
            $collection = $this->get();

            if ($this->collectionCallback) {
                $collection = call_user_func($this->collectionCallback, $collection);
            }

            $this->data = $collection->toArray();
        }

        return $this->data;
    }

    /**
     * 获取执行完所有查询后的数据集
     *
     * @return LengthAwarePaginator|Collection
     * @throws \Exception
     */
    protected function get()
    {
        if ($this->model instanceof LengthAwarePaginator) {
            return $this->model;
        }

        $this->setSort();
        $this->setPaginate();

        $this->queries->unique()->each(function ($query) {
            $this->model = call_user_func_array([$this->model, $query['method']], $query['arguments']);
        });

        if ($this->model instanceof Collection) {
            return $this->model;
        }

        if ($this->model instanceof LengthAwarePaginator) {
            $this->handleInvalidPage($this->model);

            return $this->model->getCollection();
        }

        throw new \Exception('数据查询出错');
    }

    /**
     * 如果前端设置的当前页数超过最大页数,则跳转到最大页数
     *
     * @param LengthAwarePaginator $paginator
     */
    protected function handleInvalidPage(LengthAwarePaginator $paginator)
    {
        if ($paginator->lastPage() && $paginator->currentPage() > $paginator->lastPage()) {
            $lastPageUrl = \Request::fullUrlWithQuery([
                $paginator->getPageName() => $paginator->lastPage(),
            ]);

            redirect($lastPageUrl)->send();
            die;
        }
    }

    /**
     * 设置模型查询构造器中的排序
     *
     * @throws \Exception
     */
    protected function setSort()
    {
        // 默认为主键倒序
        $this->sort = \Request::get($this->sortName, [
            'column' => $this->eloquent()->getKeyName(),
            'type'   => 'desc',
        ]);

        if (!is_array($this->sort)) {
            return;
        }

        if (empty($this->sort['column']) || empty($this->sort['type'])) {
            return;
        }

        if (str_contains($this->sort['column'], '.')) {
            $this->setRelationSort($this->sort['column']);
        } else {
            $this->resetOrderBy();

            $this->queries->push([
                'method'    => 'orderBy',
                'arguments' => [$this->sort['column'], $this->sort['type']],
            ]);
        }
    }

    /**
     * 设置关联的排序
     *
     * @param $column
     * @throws \Exception
     */
    protected function setRelationSort($column)
    {
        list($relationName, $relationColumn) = explode('.', $column);

        if (
        $this->queries->contains(function ($query) use ($relationName) {
            return $query['method'] == 'with' && in_array($relationName, $query['arguments']);
        })
        ) {
            /**
             * @var Relation $relation
             */
            $relation = $this->model->$relationName();

            $this->queries->push([
                'method'    => 'join',
                'arguments' => $this->joinParameters($relation),
            ]);

            $this->resetOrderBy();

            $this->queries->push([
                'method'    => 'orderBy',
                'arguments' => [
                    $relation->getRelated()->getTable() . '.' . $relationColumn,
                    $this->sort['type'],
                ],
            ]);
        }
    }

    /**
     * 获取join查询所需的参数
     *
     * @param Relation $relation
     * @return array
     * @throws \Exception
     */
    protected function joinParameters(Relation $relation)
    {
        $relatedTable = $relation->getRelated()->getTable();

        if ($relation instanceof BelongsTo) {
            return [
                $relatedTable,
                $relation->getForeignKey(),
                '=',
                $relatedTable . '.' . $relation->getRelated()->getKeyName(),
            ];
        }

        if ($relation instanceof HasOne) {
            return [
                $relatedTable,
                $relation->getQualifiedParentKeyName(),
                '=',
                $relation->getForeignKey(),
            ];
        }

        throw new \Exception('关联关系的排序,只支持 HasOne 和 BelongsTo 关联');
    }

    /**
     * 移除查询构造器中的排序
     */
    public function resetOrderBy()
    {
        $this->queries->reject(function ($query) {
            return $query['method'] == 'orderBy';
        });
    }

    /**
     * 设置查询构造器中的分页
     */
    protected function setPaginate()
    {
        $paginate = $this->findQueryByMethod('paginate');

        // 去掉所有分页的设置
        $this->queries = $this->queries->reject(function ($query) {
            return $query['method'] == 'paginate';
        });

        if (!$this->usePaginate) {
            $query = [
                'method'    => 'get',
                'arguments' => [],
            ];
        } else {
            $query = [
                'method'    => 'paginate',
                'arguments' => $this->resolvePerPage($paginate),
            ];
        }

        $this->queries->push($query);
    }

    /**
     * 获取分页的参数
     *
     * @param array|null $paginate
     * @return array|mixed
     */
    protected function resolvePerPage($paginate)
    {
        if ($perPage = (int)\Request::get($this->perPageName)) {
            if (is_array($paginate)) {
                $paginate['arguments'][0] = $perPage;

                return $paginate['arguments'];
            }

            $this->perPage = $perPage;
        }

        if (isset($paginate['arguments'][0])) {
            return $paginate['arguments'];
        }

        return [$this->perPage, ['*'], $this->getPageName()];
    }

    /**
     * 从查询构造器集合中,通过方法名找到该查询(方法名和参数)
     *
     * @param $method
     * @return array|null
     */
    protected function findQueryByMethod($method)
    {
        return $this->queries->first(function ($query) use ($method) {
            return $query['method'] == $method;
        });
    }

    /**
     * 用魔术方法来设置查询构造
     *
     * @param string $method
     * @param array  $arguments
     * @return $this
     */
    public function __call($method, $arguments)
    {
        $this->queries->push([
            'method'    => $method,
            'arguments' => $arguments,
        ]);

        return $this;
    }

    /**
     * 魔术方法通过字段名获取值
     *
     * @param string $key
     * @return mixed
     * @throws \Exception
     */
    public function __get($key)
    {
        $data = $this->buildData();

        if (array_key_exists($key, $data)) {
            return $data[$key];
        }

        return null;
    }

    /**
     * 添加其他查询构造
     *
     * @param array $conditions
     * @return $this
     */
    public function addConditions(array $conditions)
    {
        foreach ($conditions as $condition) {
            call_user_func_array([$this, key($condition)], current($condition));
        }

        return $this;
    }

    /**
     * 设置数据集合的回调函数
     *
     * @param \Closure|null $callback
     * @return $this
     */
    public function collection(\Closure $callback = null)
    {
        $this->collectionCallback = $callback;

        return $this;
    }

    /**
     * 返回每页数的查询字段名
     *
     * @return string
     */
    public function getPerPageName()
    {
        return $this->perPageName;
    }

    /**
     * 返回页数的查询字段名
     *
     * @return string
     */
    public function getPageName()
    {
        return $this->pageName;
    }

    /**
     * 返回排序用的查询字段名
     *
     * @return string
     */
    public function getSortName()
    {
        return $this->sortName;
    }

    /**
     * 禁用分页
     *
     * @return $this
     */
    public function disablePaginate()
    {
        return $this->usePaginate(false);
    }

    /**
     * 启用分页
     *
     * @return Model
     */
    public function enablePaginate()
    {
        return $this->usePaginate(true);
    }

    /**
     * 禁用或启用分页
     *
     * @param bool $use
     * @return $this
     */
    public function usePaginate($use = true)
    {
        $this->usePaginate = $use;

        return $this;
    }

    /**
     * 设置排序用的查询字段名
     *
     * @param string $name
     * @return $this
     */
    public function setSortName($name)
    {
        $this->sortName = $name;

        return $this;
    }

    /**
     * 设置每页数的查询字段名
     *
     * @param string $name
     * @return $this
     */
    public function setPerPageName($name)
    {
        $this->perPageName = $name;

        return $this;
    }

    /**
     * 设置页数的查询字段名
     *
     * @param string $name
     * @return $this
     */
    public function setPageName($name)
    {
        $this->pageName = $name;

        return $this;
    }
}